package com.atguigu.tingshu.search.service.impl;

import cn.hutool.extra.pinyin.PinyinUtil;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.model.album.*;
import com.atguigu.tingshu.search.service.SuggestIndexRepository;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.data.elasticsearch.core.suggest.Completion;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsAggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.aggregations.TopHitsAggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.album.AlbumFeignClient;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.AlbumInfoIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.apache.bcel.classfile.Field;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumInfoIndexRepository albumInfoIndexRepository;

    @Autowired
    private AlbumFeignClient albumFeignClient;

    @Autowired
    private UserFeignClient userFeignClient;


    @Autowired //先根据类型找Bean结果找到三个Bean 继续根据属性名称executor找对应Bean对象找不到报错；
    //@Qualifier("threadPoolTaskExecutor")
    //@Resource(name = "threadPoolTaskExecutor")
    //private Executor executor;
    private Executor threadPoolTaskExecutor;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 将指定专辑上架到索引库
     *
     * @param albumId 专辑ID
     * @return
     */
    @Override
    public void upperAlbum(Long albumId) {
        //1.创建索引库文档对象 AlbumInfoIndex
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
        //2.封装文档对象中专辑相关信息及标签列表
        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            //2.1 远程调用专辑服务获取专辑信息及标签列表
            AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
            Assert.notNull(albumInfo, "专辑:{}不存在", albumId);
            //2.2 封装专辑信息到索引库文档对象中
            BeanUtil.copyProperties(albumInfo, albumInfoIndex);
            //2.3 封装专辑标签列表到索引库文档对象中
            List<AlbumAttributeValue> albumAttributeValueVoList = albumInfo.getAlbumAttributeValueVoList();
            if (CollectionUtil.isNotEmpty(albumAttributeValueVoList)) {
                List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueVoList
                        .stream()
                        .map(albumAttributeValue -> BeanUtil.copyProperties(albumAttributeValue, AttributeValueIndex.class))
                        .collect(Collectors.toList());
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
            }
            return albumInfo;
        }, threadPoolTaskExecutor);

        //3.封装文档对象中分类相关信息
        CompletableFuture<Void> categoryCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            //3.1 远程调用专辑服务-根据专辑所属3级分类ID查询分类信息
            BaseCategoryView baseCategoryView = albumFeignClient.getCategoryView(albumInfo.getCategory3Id()).getData();
            Assert.notNull(baseCategoryView, "分类：{}不存在", albumInfo.getCategory3Id());
            //3.2 封装分类ID到索引库文档对象中
            albumInfoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
        }, threadPoolTaskExecutor);

        //4.封装文档对象中主播相关信息
        CompletableFuture<Void> announcerCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            //4.1 远程调用用户服务-根据专辑所属用户ID查询主播信息
            UserInfoVo userInfoVo = userFeignClient.getUserInfoVo(albumInfo.getUserId()).getData();
            Assert.notNull(userInfoVo, "用户：{}信息为空", albumInfo.getUserId());
            //4.2 封装主播名称到索引库文档对象中
            albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
        }, threadPoolTaskExecutor);

        //5.封装文档对象中统计相关信息 TODO 采用随机生成方式
        CompletableFuture<Void> statCompletableFuture = CompletableFuture.runAsync(() -> {
            //5.1 封装播放量数值
            int playStatNum = RandomUtil.randomInt(1000, 2000);
            albumInfoIndex.setPlayStatNum(playStatNum);
            //5.2 封装订阅量数值
            int subscribeStatNum = RandomUtil.randomInt(800, 1000);
            albumInfoIndex.setSubscribeStatNum(subscribeStatNum);
            //5.3 封装购买量数值
            int buyStatNum = RandomUtil.randomInt(100, 500);
            albumInfoIndex.setBuyStatNum(buyStatNum);
            //5.4 封装评论量数值
            int commentStatNum = RandomUtil.randomInt(500, 1000);
            albumInfoIndex.setCommentStatNum(commentStatNum);
            //5.5 基于以上生成统计数值计算出当前文档热度分值  热度=累加（不同统计数值*权重）
            BigDecimal bigDecimal1 = new BigDecimal("0.1").multiply(BigDecimal.valueOf(playStatNum));
            BigDecimal bigDecimal2 = new BigDecimal("0.2").multiply(BigDecimal.valueOf(subscribeStatNum));
            BigDecimal bigDecimal3 = new BigDecimal("0.3").multiply(BigDecimal.valueOf(buyStatNum));
            BigDecimal bigDecimal4 = new BigDecimal("0.4").multiply(BigDecimal.valueOf(commentStatNum));
            BigDecimal hotScore = bigDecimal1.add(bigDecimal2).add(bigDecimal3).add(bigDecimal4);
            albumInfoIndex.setHotScore(hotScore.doubleValue());
        }, threadPoolTaskExecutor);


        //6.组合以上四个异步任务
        CompletableFuture.allOf(
                albumInfoCompletableFuture,
                statCompletableFuture,
                categoryCompletableFuture,
                announcerCompletableFuture
        ).join();

        //6.保存专辑索引库文档对象
        albumInfoIndexRepository.save(albumInfoIndex);

        //7.将专辑标题存入提词索引库-确保专辑ID跟提词文档ID一致
        this.saveSuggestDocument(albumInfoIndex);

        //8.将上架专辑ID存入布隆过滤器
        RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
        bloomFilter.add(albumId);
    }

    /**
     * 将指定专辑下架，从索引库删除文档
     *
     * @param albumId
     * @return
     */
    @Override
    public void lowerAlbum(Long albumId) {
        //删除专辑索引库文档
        albumInfoIndexRepository.deleteById(albumId);
        //删除提词索引库中文档
        suggestIndexRepository.deleteById(albumId.toString());
    }

    @Autowired
    private ElasticsearchClient elasticsearchClient;


    //专辑索引库名称
    private static final String INDE_NAME = "album_info";

    /**
     * 整体采用传统写法，局部采用Lambda表达方式
     * 根据条件检索ElasticSearch专辑索引库
     *
     * @param albumIndexQuery 查询条件
     * @return
     */
    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        try {
            //1.基于查询条件封装检索请求对象
            SearchRequest searchRequest = this.buildDSL(albumIndexQuery);
            System.err.println("本次检索DSL：");
            System.err.println(searchRequest);
            //2.执行索引库检索
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
            //3.解析解析ES检索结果
            return this.parseSearchResult(searchResponse, albumIndexQuery);
        } catch (Exception e) {
            log.error("[检索服务]站内搜索异常：{}", e);
            throw new RuntimeException(e);
        }
    }


    /**
     * 基于检索条件对象封装检索请求对象（封装完整DSL语句）
     *
     * @param albumIndexQuery
     * @return
     */
    @Override
    public SearchRequest buildDSL(AlbumIndexQuery albumIndexQuery) {
        //1.创建检索请求构建器对象
        SearchRequest.Builder builder = new SearchRequest.Builder();
        //2.设置DSL中各项请求体参数信息：分页、高亮、排序、条件、过滤
        //2.1 设置请求路径中检索：索引库名称
        builder.index(INDE_NAME);
        //2.2 设置请求体参数中"query"查询条件
        String keyword = albumIndexQuery.getKeyword();
        //2.2.1 创建组合三大查询条件bool查询对象
        BoolQuery.Builder allConditionBoolQueryBuilder = new BoolQuery.Builder();
        //2.2.2 设置查询条件-关键字包含3个子条件（专辑标题or简介or主播名称）
        if (StringUtils.isNotBlank(keyword)) {
            allConditionBoolQueryBuilder.must(
                    m -> m.bool(
                            b -> b.should(s -> s.match(m1 -> m1.field("albumTitle").query(keyword)))
                                    .should(s -> s.match(m1 -> m1.field("albumIntro").query(keyword)))
                                    .should(s -> s.term(t -> t.field("announcerName").value(keyword)))
                    )
            );
        }
        //2.2.3 设置过滤条件-分类
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id != null) {
            allConditionBoolQueryBuilder.filter(f -> f.term(t -> t.field("category1Id").value(category1Id)));
        }
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id != null) {
            allConditionBoolQueryBuilder.filter(f -> f.term(t -> t.field("category2Id").value(category2Id)));
        }
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id != null) {
            allConditionBoolQueryBuilder.filter(f -> f.term(t -> t.field("category3Id").value(category3Id)));
        }
        //2.2.4 设置过滤条件-标签 可能提交多组标签过滤条件 一组标签条件形式=标签ID:标签值ID
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (CollectionUtil.isNotEmpty(attributeList)) {
            for (String attribute : attributeList) {
                //2.2.4.1 每循环一次设置一组标签过滤条件 标签ID:标签值ID
                String[] split = attribute.split(":");
                if (split != null && split.length == 2) {
                    allConditionBoolQueryBuilder.filter(f -> f.nested(
                            n -> n.path("attributeValueIndexList")
                                    .query(q -> q.bool(
                                            b -> b.must(m -> m.term(t -> t.field("attributeValueIndexList.attributeId").value(split[0])))
                                                    .must(m -> m.term(t -> t.field("attributeValueIndexList.valueId").value(split[1])))
                                    ))
                    ));
                }
            }
        }
        builder.query(allConditionBoolQueryBuilder.build()._toQuery());
        //2.3 设置请求体参数中"from,size"分页信息
        int from = (albumIndexQuery.getPageNo() - 1) * albumIndexQuery.getPageSize();
        builder.from(from).size(albumIndexQuery.getPageSize());
        //2.4 设置请求体参数中"sort"排序
        String order = albumIndexQuery.getOrder();
        //2.4.1 获取前端提交排序参数取值 形式=1:desc
        if (StringUtils.isNotBlank(order)) {
            String[] split = order.split(":");
            if (split != null && split.length == 2) {
                //2.4.2 获取排序字段
                String orderField = "";
                switch (split[0]) {
                    case "1":
                        orderField = "hotScore";
                        break;
                    case "2":
                        orderField = "playStatNum";
                        break;
                    case "3":
                        orderField = "createTime";
                        break;
                }
                //2.4.3 获取排序方式
                String orderDes = split[1];
                String finalOrderField = orderField;
                builder.sort(s -> s.field(f -> f.field(finalOrderField).order("asc".equals(orderDes) ? SortOrder.Asc : SortOrder.Desc)));
            }
        }
        //2.5 设置请求体参数中"highlight"高亮
        if (StringUtils.isNotBlank(keyword)) {
            builder.highlight(h -> h.fields("albumTitle", f -> f.preTags("<font style='color:red'>").postTags("</font>")));
        }
        //2.6 设置请求体参数中"_source"指定查询字段
        builder.source(s -> s.filter(f -> f.includes("id", "albumTitle", "albumIntro", "coverUrl", "includeTrackCount", "playStatNum", "createTime", "payType")));

        //3.基于构建器对象返回检索请求对象
        return builder.build();
    }

    /**
     * 解析ES检索结果
     *
     * @param searchResponse
     * @param albumIndexQuery
     * @return
     */
    @Override
    public AlbumSearchResponseVo parseSearchResult(SearchResponse<AlbumInfoIndex> searchResponse, AlbumIndexQuery albumIndexQuery) {
        //1.创建响应VO对象
        AlbumSearchResponseVo vo = new AlbumSearchResponseVo();

        //2.封装VO中四个分页相关信息
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        vo.setPageNo(pageNo);
        vo.setPageSize(pageSize);
        //2.1 解析ES获取命中记录数
        long total = searchResponse.hits().total().value();
        vo.setTotal(total);
        //2.2 根据总记录数及页大小计算总页码
        long totalPages = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
        vo.setTotalPages(totalPages);

        //3.封装Vo中检索到文档数据（记得处理高亮）
        List<Hit<AlbumInfoIndex>> hitList = searchResponse.hits().hits();
        if (CollectionUtil.isNotEmpty(hitList)) {
            //3.1 遍历命中记录对象得到Hit对象中中_source（专辑对象）
            List<AlbumInfoIndexVo> list = hitList.stream()
                    .map(hit -> {
                        AlbumInfoIndex albumInfoIndex = hit.source();
                        //3.2 处理高亮
                        Map<String, List<String>> highlightMap = hit.highlight();
                        if (CollectionUtil.isNotEmpty(highlightMap)) {
                            String highlightText = highlightMap.get("albumTitle").get(0);
                            albumInfoIndex.setAlbumTitle(highlightText);
                        }
                        return BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class);
                    }).collect(Collectors.toList());
            vo.setList(list);
        }
        //4.响应VO
        return vo;
    }

    /**
     * 查询置顶3级分类下热度TOP6专辑列表
     *
     * @param category1Id
     * @return
     */
    @Override
    public List<Map<String, Object>> searchTopCategoryHotAlbum(Long category1Id) {
        try {
            //1.根据1级分类ID远程调用专辑服务获取6个置顶三级分类 用于检索ES条件，及封装Map中分类对象
            List<BaseCategory3> category3List = albumFeignClient.getTopBaseCategory3(category1Id).getData();
            Assert.notNull(category3List, "未查询到{}置顶分类", category1Id);
            //1.1 检索多关键字精确查询查询条件FiledValue类型 将集合泛型从BaseCategory3转为FiledValue(封装3级分类ID)
            List<FieldValue> fieldValueList = category3List
                    .stream()
                    .map(category3 -> FieldValue.of(category3.getId()))
                    .collect(Collectors.toList());

            //1.2 将三级分类List转为Map<Long, BaseCategory3> Map中key:三级分类ID Value：三级分类对象
            Map<Long, BaseCategory3> category3Map = category3List
                    .stream()
                    .collect(Collectors.toMap(BaseCategory3::getId, c3 -> c3));

            //2.根据查询条件：7个三级分类ID 聚合：根据三级分类ID聚合（子聚合：按照热度进行排序）检索ES
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(
                    s -> s.index(INDE_NAME)
                            .query(q -> q.terms(t -> t.field("category3Id").terms(t1 -> t1.value(fieldValueList))))
                            .aggregations("category3_agg",
                                    a -> a.terms(t -> t.field("category3Id").size(10))
                                            .aggregations("top6", a1 -> a1.topHits(t -> t.size(6).sort(
                                                    sort -> sort.field(f -> f.field("hotScore").order(SortOrder.Desc))
                                            )))
                            )
                            .size(0),
                    AlbumInfoIndex.class);

            //2.解析ES检索结果封装置顶分类热门专辑Map对象
            //2.1 根据分类ID聚合名称从ES检索响应对象获取聚合结果
            LongTermsAggregate category3Agg = searchResponse.aggregations().get("category3_agg").lterms();
            //2.2 遍历三级分类桶（Bucket）数组,每遍历一个Bucket封装置顶分类热门专辑Map对象
            List<LongTermsBucket> bucketList = category3Agg.buckets().array();
            if (CollectionUtil.isNotEmpty(bucketList)) {
                List<Map<String, Object>> list = bucketList
                        .stream()
                        .map(c3Bucket -> {
                            //2.2.1 获取外层聚合结果中key(三级分类ID)
                            long category3Id = c3Bucket.key();
                            //2.2.2 获取当前聚合内包含子聚合（按照组内专辑热度排序）
                            TopHitsAggregate top6Aggregate = c3Bucket.aggregations().get("top6").topHits();
                            //2.2.3 遍历子聚合Bucket得到热度TOP6专辑列表
                            List<Hit<JsonData>> hitsList = top6Aggregate.hits().hits();
                            if (CollectionUtil.isNotEmpty(hitsList)) {
                                List<AlbumInfoIndex> top6List = hitsList.stream().map(hit -> {
                                    //获取热门专辑JSON字符串
                                    String sourceStr = hit.source().toString();
                                    //将JSON转为专辑文档对象
                                    AlbumInfoIndex albumInfoIndex = JSON.parseObject(sourceStr, AlbumInfoIndex.class);
                                    return albumInfoIndex;
                                }).collect(Collectors.toList());
                                //2.3 封装当前置顶分类热门专辑Map对象
                                Map<String, Object> map = new HashMap<>();
                                map.put("baseCategory3", category3Map.get(category3Id));
                                map.put("list", top6List);
                                return map;
                            }
                            return null;
                        }).collect(Collectors.toList());
                //3.返回检索结果集合对象
                return list;
            }
        } catch (IOException e) {
            log.error("[检索服务]检索置顶分类热门专辑异常：{}", e);
            throw new RuntimeException(e);
        }
        return null;
    }

    @Autowired
    private SuggestIndexRepository suggestIndexRepository;

    /**
     * 将专辑标题存入提词索引库
     *
     * @param albumInfoIndex 专辑文档
     */
    @Override
    public void saveSuggestDocument(AlbumInfoIndex albumInfoIndex) {
        //1.构建提词索引库文档对象
        SuggestIndex suggestIndex = new SuggestIndex();

        //2.为提词文档对象赋值
        suggestIndex.setId(albumInfoIndex.getId().toString());
        String albumTitle = albumInfoIndex.getAlbumTitle();
        suggestIndex.setTitle(albumTitle);
        suggestIndex.setKeyword(new Completion(new String[]{albumTitle}));
        //汉字转为拼音
        String pinyin = PinyinUtil.getPinyin(albumTitle, "");
        suggestIndex.setKeywordPinyin(new Completion(new String[]{pinyin}));
        //汉字转为拼音首字母
        String firstLetter = PinyinUtil.getFirstLetter(albumTitle, "");
        suggestIndex.setKeywordSequence(new Completion(new String[]{firstLetter}));
        //3.保存提词文档记录
        suggestIndexRepository.save(suggestIndex);
    }


    private static final String SUGGEST_INDEX = "suggestinfo";

    /**
     * 根据用户已录入字符查询提词索引库进行自动补全关键字
     *
     * @param keyword
     * @return
     */
    @Override
    public List<String> completeSuggest(String keyword) {
        try {
            //1.发起自动补全请求
            SearchResponse<SuggestIndex> searchResponse = elasticsearchClient.search(s ->
                            s.index(SUGGEST_INDEX)
                                    .suggest(
                                            s1 -> s1.suggesters("letter-suggest", fs -> fs.prefix(keyword).completion(c -> c.field("keywordSequence").size(10).skipDuplicates(true)))
                                                    .suggesters("pinyin-suggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keywordPinyin").size(10).skipDuplicates(true)))
                                                    .suggesters("keyword-suggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keyword").size(10).skipDuplicates(true)))
                                    )
                    , SuggestIndex.class);
            //2.解析ES自动补全结果
            Set<String> hashSet = new HashSet<>();
            //2.1 解析建议结果-通过不同建议参数名获取汉字、拼音等提示词结果
            hashSet.addAll(this.parseSuggestResult(searchResponse, "letter-suggest"));
            hashSet.addAll(this.parseSuggestResult(searchResponse, "pinyin-suggest"));
            hashSet.addAll(this.parseSuggestResult(searchResponse, "keyword-suggest"));
            //2.2 如果解析建议提示词列表长度小于10，采用全文查询尝试补全到10个
            if (hashSet.size() < 10) {
                //2.2.1 根据用户录入字符进行全文检索
                SearchResponse<AlbumInfoIndex> matchSearchResponse = elasticsearchClient.search(
                        s -> s.index(INDE_NAME)
                                .query(q -> q.match(m -> m.field("albumTitle").query(keyword)))
                                .size(10)
                        , AlbumInfoIndex.class
                );
                HitsMetadata<AlbumInfoIndex> hits = matchSearchResponse.hits();
                List<Hit<AlbumInfoIndex>> hitList = hits.hits();
                if (CollectionUtil.isNotEmpty(hitList)) {
                    for (Hit<AlbumInfoIndex> hit : hitList) {
                        AlbumInfoIndex source = hit.source();
                        //2.2.2 将检索到专辑标题内容加入到提词结果列表中
                        hashSet.add(source.getAlbumTitle());
                        if (hashSet.size() >= 10) {
                            break;
                        }
                    }
                }
            }
            if (hashSet.size() >= 10) {
                //如果提词结果列表中大于10截取前10个
                return new ArrayList<>(hashSet).subList(0, 10);
            } else {
                return new ArrayList<>(hashSet);
            }
        } catch (IOException e) {
            log.error("[搜索服务]关键字自动补全异常：{}", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 解析建议词结果
     *
     * @param searchResponse ES检索结果对象
     * @param suggestName    自定义建议词参数名称
     * @return
     */
    @Override
    public List<String> parseSuggestResult(SearchResponse<SuggestIndex> searchResponse, String suggestName) {
        //根据自定义建议词参数名称获取结果列表
        List<String> list = new ArrayList<>();
        List<Suggestion<SuggestIndex>> suggestionList = searchResponse.suggest().get(suggestName);
        if (CollectionUtil.isNotEmpty(suggestionList)) {
            //遍历得到建议对象
            for (Suggestion<SuggestIndex> suggestIndexSuggestion : suggestionList) {
                for (CompletionSuggestOption<SuggestIndex> option : suggestIndexSuggestion.completion().options()) {
                    SuggestIndex suggestIndex = option.source();
                    list.add(suggestIndex.getTitle());
                }
            }
        }
        return list;
    }

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 更新不同分类不同排行TOP20数据
     *
     * @return
     */
    @Override
    public void updateLatelyAlbumRanking() {
        try {
            //1.远程调用专辑服务获取所有1级分类获取一级分类ID列表
            List<BaseCategory1> baseCategory1List = albumFeignClient.getAllCategory1().getData();
            if (CollectionUtil.isNotEmpty(baseCategory1List)) {
                //2.遍历所有一级分类ID-根据分类ID+排序方式+限制返回文档数量
                List<Long> category1IdList = baseCategory1List.stream().map(BaseCategory1::getId).collect(Collectors.toList());

                for (Long category1Id : category1IdList) {

                    //声明当前分类hash结构Key
                    String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
                    //处理某个一级分类排行数据，遍历5种不同排序字段
                    String[] rankingDimensionArray = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};
                    for (String rankingDimension : rankingDimensionArray) {
                        SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(
                                s -> s.index(INDE_NAME)
                                        .query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
                                        .sort(sort -> sort.field(f -> f.field(rankingDimension).order(SortOrder.Desc)))
                                        .size(20)
                                        .source(s1 -> s1.filter(f -> f.includes("id", "albumTitle", "albumIntro", "coverUrl", "includeTrackCount", "playStatNum", "createTime", "payType"))),
                                AlbumInfoIndex.class
                        );
                        //3.解析当前分类下当前排序方式命中TOP20专辑文档列表
                        List<Hit<AlbumInfoIndex>> hitList = searchResponse.hits().hits();
                        if (CollectionUtil.isNotEmpty(hitList)) {
                            //3.1 遍历命中记录对象得到Hit对象中中_source（专辑对象）
                            List<AlbumInfoIndexVo> top20List = hitList.stream()
                                    .map(hit -> {
                                        AlbumInfoIndex albumInfoIndex = hit.source();
                                        //3.2 处理高亮
                                        Map<String, List<String>> highlightMap = hit.highlight();
                                        if (CollectionUtil.isNotEmpty(highlightMap)) {
                                            String highlightText = highlightMap.get("albumTitle").get(0);
                                            albumInfoIndex.setAlbumTitle(highlightText);
                                        }
                                        return BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class);
                                    }).collect(Collectors.toList());
                            //4.将不同分类不同维度的TOP20数据放入Redis
                            redisTemplate.opsForHash().put(key, rankingDimension, top20List);
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("[搜索服务]更新排行榜Redis异常：{}", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取不同分类下不同排行维度TOP20
     *
     * @param category1Id
     * @param dimension
     * @return
     */
    @Override
    public List<AlbumInfoIndexVo> getRankingList(Long category1Id, String dimension) {
        //方式一：根据key+filed获取hash的VALUE
        String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
        //List<AlbumInfoIndexVo> list = (List<AlbumInfoIndexVo>) redisTemplate.opsForHash().get(key, dimension);
        //方式二：创建操作hash对象
        BoundHashOperations<String, String, List<AlbumInfoIndexVo>> boundHashOperations = redisTemplate.boundHashOps(key);
        List<AlbumInfoIndexVo> list = boundHashOperations.get(dimension);
        return list;
    }
}
